/*
 * $Id: GeneratingLoggerFactory.java 114 2013-01-16 07:33:27Z vladimirow@mail.ru $
 * $HeadURL: http://ultralog.googlecode.com/svn/ultralog/trunk/src/java/com/google/code/ultralog/factory/GeneratingLoggerFactory.java $
 *
 * This file is a part of ultralog project (http://code.google.com/p/ultralog/).
 * Released under New BSD License.
 */
package com.google.code.ultralog.factory;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

import com.google.code.ultralog.Logger;
import com.google.code.ultralog.configuration.LoggerConfiguration;
import com.google.code.ultralog.configuration.LoggerConfigurationProvider;
import com.google.code.ultralog.formatter.FormatterFactory;

/**
 * An implementation of {@link LoggerFactory} interface that uses code
 * generation to generate logger implementations classes on the fly.
 *
 * @author Mikhail Vladimirov
 */
public class GeneratingLoggerFactory implements LoggerFactory
{
    private final LoggerConfigurationProvider loggerConfigurationProvider;
    private final FormatterFactory formatterFactory;

    private final Map <Class <? extends Logger>,
         LoggerInstantiator <? extends Logger>> instantiators =
             new HashMap <Class<? extends Logger>,
                          LoggerInstantiator<? extends Logger>> ();

    /**
     * Create new generating logger factory that uses given logger configuration
     * provider and formatter factory.
     *
     * @param loggerConfigurationProvider logger confuguration provider to use
     * @param formatterFactory formatter factory to use
     */
    public GeneratingLoggerFactory (
        LoggerConfigurationProvider loggerConfigurationProvider,
        FormatterFactory formatterFactory)
    {
        if (loggerConfigurationProvider == null)
            throw new IllegalArgumentException (
                "Logger configuration provider is null");

        if (formatterFactory == null)
            throw new IllegalArgumentException ("Formatter factory is null");

        this.loggerConfigurationProvider = loggerConfigurationProvider;
        this.formatterFactory = formatterFactory;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public synchronized <T extends Logger> T createLogger (
        Class <T> loggerInterface,
        String loggerName)
    {
        if (loggerInterface == null)
            throw new IllegalArgumentException ("Logger interface is null");

        if (loggerName == null)
            throw new IllegalArgumentException ("Logger name is null");

        LoggerInstantiator <T> instantiator =
            (LoggerInstantiator <T>)instantiators.get (loggerInterface);

        if (instantiator == null)
        {
            instantiator = new LoggerInstantiator <T> (loggerInterface);
            instantiators.put (loggerInterface, instantiator);
        }

        return instantiator.instantiate (
            loggerName,
            loggerConfigurationProvider.getConfiguration (loggerName),
            formatterFactory);
    }

    private static class LoggerInstantiator <T extends Logger>
    {
        private final Class <? extends T> loggerClass;
        private final Constructor <? extends T> loggerConstructor;

        @SuppressWarnings("unchecked")
        public LoggerInstantiator (Class <T> loggerInterface)
        {
            if (loggerInterface == null)
                throw new IllegalArgumentException ("Logger interface is null");

            if (!loggerInterface.isInterface ())
                throw new IllegalArgumentException (
                    "Logger interface (" + loggerInterface +
                    ") is not an interface");

            String loggerClassName = loggerInterface.getName () + "$Impl";

            Class <? extends T> loggerClass = null;
            Constructor <? extends T> loggerConstructor = null;

            try
            {
                Class <?> c = Class.forName (loggerClassName);

                boolean valid = true;

                if (valid && !loggerInterface.isAssignableFrom (c))
                {
                    System.err.println (
                        "Class " + c + " does not implement interface " +
                        loggerInterface);
                    valid = false;
                }

                if (valid && c.isInterface ())
                {
                    System.err.println ("Class " + c + " is an interface");
                    valid = false;
                }

                int modifiers = c.getModifiers ();

                if (valid && Modifier.isAbstract (modifiers))
                {
                    System.err.println ("Class " + c + " is abstract");
                    valid = false;
                }

                if (valid && !Modifier.isPublic (modifiers))
                {
                    System.err.println ("Class " + c + " is not public");
                    valid = false;
                }

                if (valid)
                {
                    try
                    {
                        loggerConstructor = (Constructor <? extends T>)c.
                            getConstructor (
                                String.class,
                                LoggerConfiguration.class,
                                FormatterFactory.class);
                    }
                    catch (NoSuchMethodException ex)
                    {
                        System.err.println (
                            "Class " + c + " does not have proper constructor");
                        valid = false;
                    }
                }

                if (valid)
                    loggerClass = (Class <? extends T>)c;
            }
            catch (ClassNotFoundException ex)
            {
                // Ignore
            }

            if (loggerClass == null)
            {
                GeneratingClassLoader <T> classLoader =
                    new GeneratingClassLoader <T> (
                        loggerInterface.getClassLoader ());

                loggerClass = classLoader.generateLoggerClass (
                    loggerClassName, loggerInterface);

                try
                {
                    loggerConstructor = (Constructor <? extends T>)loggerClass.
                        getConstructor (
                            String.class,
                            LoggerConfiguration.class,
                            FormatterFactory.class);
                }
                catch (NoSuchMethodException ex)
                {
                    throw new Error (
                        "Generated logger class " + loggerClass +
                        " does not have proper constructor");
                }
            }

            this.loggerClass = loggerClass;
            this.loggerConstructor = loggerConstructor;
        }

        public T instantiate (
            String loggerName,
            LoggerConfiguration loggerConfiguration,
            FormatterFactory formatterFactory)
        {
            if (loggerName == null)
                throw new IllegalArgumentException ("Logger name is null");

            if (loggerConfiguration == null)
                throw new IllegalArgumentException (
                    "Logger configuration is null");

            if (formatterFactory == null)
                throw new IllegalArgumentException (
                    "Formatter factory is null");

            try
            {
                return loggerConstructor.newInstance (
                    loggerName,
                    loggerConfiguration,
                    formatterFactory);
            }
            catch (InvocationTargetException ex)
            {
                throw new Error (
                    "Exception occurred while instantiating class " +
                    loggerClass + " using constructor " + loggerConstructor,
                    ex.getTargetException ());
            }
            catch (InstantiationException ex)
            {
                throw new Error (
                    "Cannot instantiate class " + loggerClass +
                    " using constructor " + loggerConstructor, ex);
            }
            catch (IllegalAccessException ex)
            {
                throw new Error (
                    "Cannot access constructor " + loggerConstructor +
                    " in logger class " + loggerClass, ex);
            }
        }

        private static class GeneratingClassLoader <T extends Logger>
            extends ClassLoader
        {
            public GeneratingClassLoader (ClassLoader parent)
            {
                super (parent);
            }

            @SuppressWarnings("unchecked")
            public Class <? extends T> generateLoggerClass (
                String name, Class <T> loggerInterface)
            {
                if (name == null)
                    throw new IllegalArgumentException ("Name is null");

                if (loggerInterface == null)
                    throw new IllegalArgumentException (
                        "Logger interface is null");

                LoggerClassGenerator generator =
                    new LoggerClassGenerator (
                        LoggerInterfaceUtils.describeLoggerInterface (
                            loggerInterface));

                byte [] classBody = generator.generateLoggerClass (
                    loggerInterface.getName (), name);

                return (Class <? extends T>) defineClass (
                    name, classBody, 0, classBody.length);
            }
        }
    }
}
